package ui;

import engine.Boid;
import engine.Vector2D;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import javax.swing.BorderFactory;
import javax.swing.JPanel;

public class DrawingScreen extends JPanel implements MouseListener, MouseMotionListener, Runnable {

    private Interface gui;
    private Integer width;
    private Integer height;
    private Color backGroundColor;
    private boolean running;
    private long oneSecond;
    private Integer fpsCount;
    private Integer fps;
    private ArrayList<GraphicObject> objectsToDraw;
    private ArrayList<Boid> boidsToDraw;
    private Boid currentBoid;
    private GraphicLine curSpeedVector;

    //Limites de l'écran
    public class Bounds {

        public Integer minX, maxX;
        public Integer minY, maxY;

        public Bounds(Integer minX, Integer maxX, Integer minY, Integer maxY) {
            this.minX = minX;
            this.maxX = maxX;
            this.minY = minY;
            this.maxY = maxY;
        }
    }

    public DrawingScreen(int width, int height, Color backGroundColor, Interface gui) {
        //propritétés du panel parent
        super();
        super.setSize(width, height);
        super.setDoubleBuffered(true);
        super.setBackground(backGroundColor);
        super.setBorder(BorderFactory.createLineBorder(Color.black));
        this.gui = gui;

        //propiétés principales
        this.width = width;
        this.height = height;
        this.backGroundColor = backGroundColor;

        //Mouse linsteners
        this.addMouseListener(this);
        this.addMouseMotionListener(this);

        running = true;

        //liste des objets à dessiner
        objectsToDraw = new ArrayList<GraphicObject>();
        boidsToDraw = new ArrayList<Boid>();

        oneSecond = 0;
        fpsCount = 0;
        fps = 0;
    }

    @Override
    public void paintComponent(Graphics g) {
        //System.out.println("paintComponent called");

        //Remplissage de la couleur de fond
        g.setColor(backGroundColor);
        g.fillRect(0, 0, width, height);

        //Dessine chaque objet
        synchronized (objectsToDraw) {
            for (GraphicObject obj : objectsToDraw) {
                obj.draw(g);
            }
        }

        synchronized (boidsToDraw) {
            for (Boid boid : boidsToDraw) {
                boid.draw(g);
            }
        }


        //Ecrit fps en haut à gauche
        g.setColor(Color.BLACK);
        g.drawString("FPS " + fps.toString(), 5, 20);
    }

    @Override
    public void mouseClicked(MouseEvent e) {
        //System.out.println("mouseClicked");
    }

    @Override
    public void mousePressed(MouseEvent e) {
        Double mouseX = (double) e.getX();
        Double mouseY = (double) e.getY();
        Vector2D mousePoint = new Vector2D(mouseX, mouseY, 1.);

        //System.out.println("mousePressed ("+e.getX()+"; "+e.getY()+")");

        //Ajout d'un boid aux coords de la souris
        Integer boidRayon = (gui != null) ? gui.getBoidRayon() : 5;
        currentBoid = new Boid(mousePoint, null, boidRayon, Color.RED, Color.BLACK, gui);

        //Ajout du vecteur depuis le boid vers la souris
        curSpeedVector = new GraphicLine(mousePoint, mousePoint, null, Color.DARK_GRAY);

        //Ajouts synchronisés car cette liste est lue par le thread
        synchronized (objectsToDraw) {
            objectsToDraw.add(curSpeedVector);
        }

        synchronized (boidsToDraw) {
            boidsToDraw.add(currentBoid);
        }
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        //System.out.println("mouseReleased");

        //Le vecteur vitesse courant est affecté au boid courant, puis effacé
        currentBoid.setSpeed(Vector2D.div(curSpeedVector.calcVector2D(), GraphicLine.speedUnitToPixelRatio));

        synchronized (objectsToDraw) {
            objectsToDraw.remove(curSpeedVector);
        }
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        //System.out.println("mouseDragged");

        Double mouseX = (double) e.getX();
        Double mouseY = (double) e.getY();
        Vector2D mousePoint = new Vector2D(mouseX, mouseY, 1.);

        //Déplacement du vecteur vitesse aux coordonnées de la souris
        curSpeedVector.setP2(mousePoint);
    }

    @Override
    public void mouseMoved(MouseEvent e) {
        //System.out.println("mouseMoved");
    }

    public void reset() {
        synchronized (objectsToDraw) {
            objectsToDraw.clear();
        }
        synchronized (boidsToDraw){
            boidsToDraw.clear();
        }
    }

    @Override
    public void run() {
        while (running) {
            //System.out.println("repaint");

            long timeBeforeFrame = System.currentTimeMillis();

            //Repositionne chaque objet en additionnant leur vecteur vitesse à leur position
            //Si touche un mur, inversion du vecteur dans direction opposé du contact
            synchronized (boidsToDraw) {//Synchronisé car la liste est potentiellement modifiée pendant sa lecture

                Vector2D desiredDirection;
                for (Boid boid : boidsToDraw) {
                    desiredDirection = new Vector2D(0., 0., 0.);

                    if (boid.getSpeed() != null) {
                        //Màj de la vitesse en fonction de l'ui
                        Double speedCoefficient = (gui != null) ? new Double(gui.getSpeed()) : 5. ;
                        boid.setCoeffSpeed(speedCoefficient);
                        
                        //Déplacement (inclus rebond sur les murs)
                        boid.move(new Bounds(0, width, 0, height));

                        //Boids proches
                        ArrayList<Boid> closeBoids = boid.closeBoids(boidsToDraw);

                        //On recherche la direction souhaitée
                        Double alignementValue = (gui != null) ? gui.getAlignementValue() / 10. : 1. / 3.;
                        Double aggregationValue = (gui != null) ? gui.getAggregationValue() / 10. : 1. / 3.;
                        Double repulsionValue = (gui != null) ? gui.getRepulsionValue() / 10. : 1. / 3.;

                        desiredDirection = Vector2D.add(desiredDirection, Vector2D.mul(boid.align(closeBoids), alignementValue));
                        desiredDirection = Vector2D.add(desiredDirection, Vector2D.mul(boid.repulsion(closeBoids), repulsionValue));
                        desiredDirection = Vector2D.add(desiredDirection, Vector2D.mul(boid.aggregation(closeBoids), aggregationValue));
                        boid.setDesiredDirection(desiredDirection);

                        //Rotation vers la direction souhaitée
                        boid.rotateToDesiredDirection();
                    }
                }
            }

            //Redessiner tout
            repaint();

            //Attente de qq millisecondes pour ne pas redessiner trop vite
            try {
                Thread.sleep(10);
            } catch (InterruptedException ex) {
            }

            long timeAfterFrame = System.currentTimeMillis();

            //nombre de millisecondes écoulées pour le calcul de cette image
            long frameDuration = timeAfterFrame - timeBeforeFrame;

            //Mise à jour du FPS
            oneSecond += frameDuration;
            fpsCount++;

            if (oneSecond >= 1000) {
                fps = fpsCount;
                fpsCount = 0;
                oneSecond = 0;
            }
        }
    }
}
